package com.swensen.game.editor;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;

import javax.swing.JOptionPane;

import com.swensen.game.Board;
import com.swensen.game.data.GameState;
import com.swensen.game.data.Tile;
import com.swensen.game.data.TileMap;
import com.swensen.game.data.BoardData.LevelType;
import com.swensen.game.editor.data.Cursor;
import com.swensen.game.editor.data.EditorBoardData;
import com.swensen.game.editor.event.EditorCycleEvent;
import com.swensen.game.editor.event.EditorCycleEventListener;
import com.swensen.game.editor.event.EditorEventMulticaster;

/**
 * The Class EditorBoard.
 */
public class EditorBoard extends Board {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 1786712415810166470L;
	
	/** The draw. */
	private boolean draw;
	
	/** The editor cycle event listener. */
	transient EditorCycleEventListener editorCycleEventListener;
	
	/**
	 * Instantiates a new editor board.
	 */
	public EditorBoard() {
		super(new Cursor());
		centerBounds();
	}
	
	/**
	 * Adds the editory cycle event listener.
	 *
	 * @param l the l
	 */
	public void addEditoryCycleEventListener(EditorCycleEventListener l) {
		editorCycleEventListener = EditorEventMulticaster.add(editorCycleEventListener, l);
	}
	
	/* (non-Javadoc)
	 * @see com.swensen.game.Board#boardKeyPressed(java.awt.event.KeyEvent)
	 */
	@Override
	protected void boardKeyPressed(final KeyEvent e) {
		synchronized(animationLock) {
			fsprite.keyPressed(e);
			switch(e.getKeyCode()) {
				case KeyEvent.VK_C:
					askToClear();
					break;
				case KeyEvent.VK_F:
					fillTiles(GameState.INSTANCE.<EditorBoardData>getBoardData().getLevel());
					break;
				case KeyEvent.VK_SPACE:
					draw = true;
					setTile();
			}
		}
	}
	
	private void askToClear() {
		int result = JOptionPane.showConfirmDialog(this,
				"Are you certain you want to clear the map?",
				"Warning",
				JOptionPane.YES_NO_OPTION);
		if (result == JOptionPane.YES_OPTION) {
			tiles.clear();
		}
	}

	/* (non-Javadoc)
	 * @see com.swensen.game.Board#boardKeyReleased(java.awt.event.KeyEvent)
	 */
	@Override
	protected void boardKeyReleased(final KeyEvent e) {
		synchronized(animationLock) {
			fsprite.keyReleased(e);
			switch(e.getKeyCode()) {
				case KeyEvent.VK_SPACE:
					draw = false;
			}
		}
	}
	
	/**
	 * Center bounds.
	 */
	private void centerBounds() {
		final int boardWidth = getWidth();
		final int boardHeight = getHeight();
		final int oldx = boardBounds.getMinX();
		final int oldy = boardBounds.getMinY();
		int x;
		synchronized(animationLock) {
			if ((x = ((boardWidth - boardBounds.getWidth())) / 2) >= 0) {
				viewPort.setMinX(0);
				boardBounds.setMinX(x);
			}
			if ((x = ((boardHeight - boardBounds.getHeight())) / 2) >= 0) {
				viewPort.setMinY(0);
				boardBounds.setMinY(x);
			}
			fsprite.translate(boardBounds.getMinX() - oldx, boardBounds.getMinY() - oldy);
		}
	}

	/* (non-Javadoc)
	 * @see com.swensen.game.Board#componentResized(java.awt.event.ComponentEvent)
	 */
	@Override
	public void componentResized(final ComponentEvent e) {
		synchronized(animationLock) {
			centerBounds();
			viewPort.setHeight(getHeight());
			viewPort.setWidth(getWidth());
			if (viewPort.getMinX() > 0 && viewPort.getMaxX() > boardBounds.getMaxX()) {
				viewPort.setMinX(Math.max(boardBounds.getMinX(), viewPort.getMinX() - (viewPort.getMaxX() - boardBounds.getMaxX())));
			}
			if (viewPort.getMinY() > 0 && viewPort.getMaxY() > boardBounds.getMaxY()) {
				viewPort.setMinY(Math.max(boardBounds.getMinY(), viewPort.getMinY() - (viewPort.getMaxY() - boardBounds.getMaxY())));
			}
			repaint();
		}
	}

	/* (non-Javadoc)
	 * @see com.swensen.game.Board#cycle()
	 */
	@Override
	protected void cycle() {
		synchronized(animationLock) {
			fsprite.move(boardBounds);
			moveViewPort();
			
			if (draw) {
				setTile();
			}
			
			if (editorCycleEventListener != null) {
				editorCycleEventListener.cycleDispatched(new EditorCycleEvent(this));
			}
		}
		repaint();
	}
	
	/* (non-Javadoc)
	 * @see com.swensen.game.Board#drawBoard(java.awt.Graphics2D)
	 */
	@Override
	protected void drawBoard(final Graphics2D g) {
		drawTiles(g);
		drawBounds(g);
		
		if (drawStartTime / 500 % 2 == 0) {
			drawCursor(g, true);
		}
		else {
			drawCursor(g, false);
		}
		
		g.setColor(Color.WHITE);
	}
	
	/**
	 * Draw bounds.
	 *
	 * @param g the g
	 */
	private void drawBounds(final Graphics2D g) {
		final Color color = g.getColor();
		g.setColor(Color.RED);
		final Point toDraw = boardPointToViewPort(boardBounds.getMinX(), boardBounds.getMinY());
		g.drawRect(toDraw.x - 1,
				   (toDraw.y - 1),
				   (boardBounds.getWidth() + 2),
				   (boardBounds.getHeight() + 2));
		g.setColor(color);
	}
	
	/**
	 * Draw cursor.
	 *
	 * @param g the g
	 * @param fill the fill
	 */
	private void drawCursor(final Graphics2D g, final boolean fill) {
		final Color color = g.getColor();
		g.setColor(Color.WHITE);

		float a = 1.0f;
		final Point toDraw = boardPointToViewPort(fsprite.x(), fsprite.y());
		BufferedImage image;
		if ((image = fsprite.getImage()) != null) {
			g.drawImage(image, toDraw.x, toDraw.y, image.getWidth(), image.getHeight(), null);
			a = .7f;
		}
		
		if (fill) {
			Composite comp = g.getComposite();
			g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, a));
			g.fill(new Rectangle(toDraw.x,
					toDraw.y, Tile.TILE_SIZE, Tile.TILE_SIZE));
			g.setComposite(comp);
		}
		else {
			
			g.draw(new Rectangle(toDraw.x,
					toDraw.y, Tile.TILE_SIZE, Tile.TILE_SIZE));
		}
		g.setColor(color);
	}
	
	/**
	 * Draw tile.
	 */
	private void setTile() {
		Point toDraw = getCursorCurrentTileMapPosition();
		setTile(toDraw.x, toDraw.y);
	}

	/**
	 * Draw tile.
	 *
	 * @param x the x
	 * @param y the y
	 */
	private void setTile(int x, int y) {
		EditorBoardData data = GameState.INSTANCE.getBoardData();
		tiles.setTile(data.getCurrentTile().copy(), data.getLevel(), x, y);
	}

	/**
	 * Draw tiles.
	 *
	 * @param g the g
	 */
	private void drawTiles(Graphics2D g) {
		int level = GameState.INSTANCE.<EditorBoardData>getBoardData().getLevel();
		for (int z = 0; z < LevelType.COUNT; ++z) {
			for(int y = 0; y < tiles.getHeight(); ++y) {
				for (int x = 0; x < tiles.getWidth(); ++x) {
					BufferedImage i = tiles.getImage(z, x, y);
					if (i == null) {
						continue;
					}
					
					Point toDraw = boardPointToViewPort(x * Tile.TILE_SIZE, y * Tile.TILE_SIZE);
					toDraw.x += boardBounds.getMinX();
					toDraw.y += boardBounds.getMinY();
					
					Composite comp = g.getComposite();
					float a = level < z ? 1.0F - (.3F * (z - level)) : 1.0F;
					g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, a));
					g.drawImage(i, toDraw.x, toDraw.y, i.getWidth(), i.getHeight(), null);
					g.setComposite(comp);
				}
			}
		}
	}
	
	
	/**
	 * Fill tiles.
	 */
	private void fillTiles(int level) {
		Point center = getCursorCurrentTileMapPosition();
		Tile current = tiles.getTile(level, center.x, center.y);
		if (tiles.isEmptyOrCurrentTile(current, level, center.x, center.y) ) {
			fillTiles(current, level, center.x, center.y);
		}
	}

	/**
	 * Fill tiles.
	 * @param current 
	 *
	 * @param x the x
	 * @param y the y
	 */
	private void fillTiles(Tile current, int level, int x, int y) {
		setTile(x, y);
		for (int i = Math.max(x - 1, 0); i <= Math.min(x + 1, tiles.getWidth() - 1); ++i) {
			for (int j = Math.max(y - 1, 0); j <= Math.min(y + 1, tiles.getHeight() - 1); ++j) {
				if (tiles.isEmptyOrCurrentTile(current, level, i, j)) {
					fillTiles(current, level, i, j);
				}
			}
		}
	}

	/**
	 * Gets the cursor current tile map position.
	 *
	 * @return the cursor current tile map position
	 */
	private Point getCursorCurrentTileMapPosition() {
		return new Point((fsprite.x() - boardBounds.getMinX()) / Tile.TILE_SIZE,
				(fsprite.y() - boardBounds.getMinY()) / Tile.TILE_SIZE);
	}
}
